/*
 * Copyright (c) 2018 THL A29 Limited, a Tencent company. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.luxor.sdk.common;

import org.luxor.sdk.common.exception.LuxorCloudSDKException;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.io.IOException;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.TreeMap;

/**
 * 接口签名工具类
 *
 * @author Mr.Yan  @date: 2022/5/18
 */
public class Sign {
    private static final Charset UTF8 = StandardCharsets.UTF_8;
    private static final char[] DIGITS = {'0', '1', '2', '3', '4', '5', '6',
            '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
    public static final String SIG_METHOD = "HmacSHA256";
    public static final String SIGNATURE = "signature";
    public static final String CLIENT_ID = "client_id";
    public static final String TIMESTAMP = "timestamp";
    public static final String NONCE = "nonce";

    /**
     * 签名
     *
     * @param clientSecret  客户端秘钥
     * @param signPlainText 签名内容
     * @param sigMethod     签名算法
     * @return java.lang.String
     */
    public static String sign(String clientSecret, String signPlainText, String sigMethod) throws IOException {
        try {
            Mac mac = Mac.getInstance(sigMethod);
            byte[] hash;
            SecretKeySpec secretKeySpec = new SecretKeySpec(clientSecret.getBytes(UTF8), mac.getAlgorithm());
            mac.init(secretKeySpec);
            hash = mac.doFinal(signPlainText.getBytes(UTF8));
            return DatatypeConverter.printBase64Binary(hash);
        } catch (GeneralSecurityException e) {
            throw new IOException(e.getMessage(), e.getCause());
        }

    }

    /**
     * 将http请求内容转换为纯文本
     * <p/>
     * ?paramName1=paramValue1&paramName2=paramValue2&name=颜新明#body=289DFF07669D7A23DE0EF88D2F7129E7{clientSecret}
     *
     * @param clientSecret 客户端秘钥
     * @param queryParams  请求参数
     * @param requestBody  请求报文
     */
    public static String makeSignPlainText(String clientSecret, TreeMap<String, String> queryParams, byte[] requestBody) throws IOException {
        if (requestBody == null || requestBody.length == 0) {
            requestBody = "".getBytes();
        }
        return buildParamStr(queryParams) + "#body=" + md5(requestBody) + "{" + clientSecret + "}";
    }

    protected static String buildParamStr(TreeMap<String, String> queryParams) throws IOException {
        String retStr = "";
        for (String name : queryParams.keySet()) {
            String value = queryParams.get(name);
            if (SIGNATURE.equals(name) || name == null || name.trim().isEmpty() || value == null || value.trim().isEmpty()) {
                continue;
            }
            if (retStr.isEmpty()) {
                retStr += "?";
            } else {
                retStr += "&";
            }
            retStr += name + "=" + URLDecoder.decode(value, StandardCharsets.UTF_8.name());
        }
        return retStr;
    }

    private static String md5(byte[] input) {
        MessageDigest msgDigest;
        try {
            msgDigest = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException e) {
            throw new IllegalStateException("System doesn't support MD5 algorithm.");
        }
        msgDigest.update(input);
        byte[] bytes = msgDigest.digest();
        return new String(encodeHex(bytes));
    }

    /**
     * 转化为大写字母
     */
    private static char[] encodeHex(byte[] data) {
        int l = data.length;
        char[] out = new char[l << 1];
        for (int i = 0, j = 0; i < l; i++) {
            out[j++] = DIGITS[(0xF0 & data[i]) >>> 4];
            out[j++] = DIGITS[0x0F & data[i]];
        }
        return out;
    }

    public static String sha256Hex(String s) throws LuxorCloudSDKException {
        MessageDigest md;
        try {
            md = MessageDigest.getInstance("SHA-256");
        } catch (NoSuchAlgorithmException e) {
            throw new LuxorCloudSDKException("SHA-256 is not supported." + e.getMessage());
        }
        byte[] d = md.digest(s.getBytes(UTF8));
        return DatatypeConverter.printHexBinary(d).toLowerCase();
    }

    public static String sha256Hex(byte[] b) throws LuxorCloudSDKException {
        MessageDigest md;
        try {
            md = MessageDigest.getInstance("SHA-256");
        } catch (NoSuchAlgorithmException e) {
            throw new LuxorCloudSDKException("SHA-256 is not supported." + e.getMessage());
        }
        byte[] d = md.digest(b);
        return DatatypeConverter.printHexBinary(d).toLowerCase();
    }

    public static byte[] hmac256(byte[] key, String msg) throws LuxorCloudSDKException {
        Mac mac;
        try {
            mac = Mac.getInstance("HmacSHA256");
        } catch (NoSuchAlgorithmException e) {
            throw new LuxorCloudSDKException("HmacSHA256 is not supported." + e.getMessage());
        }
        SecretKeySpec secretKeySpec = new SecretKeySpec(key, mac.getAlgorithm());
        try {
            mac.init(secretKeySpec);
        } catch (InvalidKeyException e) {
            throw new LuxorCloudSDKException(e.getClass().getName() + "-" + e.getMessage());
        }
        return mac.doFinal(msg.getBytes(UTF8));
    }
}
